Skip to content

[codex] Add JS virtual filesystem mounts#482

Open
ex3ndr wants to merge 1 commit into
pydantic:mainfrom
ex3ndr:codex/js-virtual-mounts
Open

[codex] Add JS virtual filesystem mounts#482
ex3ndr wants to merge 1 commit into
pydantic:mainfrom
ex3ndr:codex/js-virtual-mounts

Conversation

@ex3ndr

@ex3ndr ex3ndr commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds first-class JavaScript virtual filesystem mounts for Monty, independent of the external-function support path.

  • adds a native paused OS-call surface so unsupported filesystem calls can be handled by JS and resumed
  • keeps native MountDir handling as the fast path and falls back to paused OS calls only when needed
  • adds VirtualMount support in the JS wrapper with sync and async backends, read-only/read-write modes, write limits, and path routing
  • extends REPL execution with feedStart, sync feed, and async feedAsync support for paused filesystem calls
  • adds marker conversions for Monty path, file handle, and stat result values returned from JS

Validation

  • cargo fmt
  • cargo check -p monty-js
  • npm run build:debug
  • npm test -- __test__/virtual_mount.spec.ts
  • npm test -- __test__/mount.spec.ts __test__/start.spec.ts __test__/repl.spec.ts __test__/async.spec.ts __test__/virtual_mount.spec.ts
  • npm run lint
  • cargo test -p monty-js
  • git diff --check
  • npm test

@codspeed-hq

codspeed-hq Bot commented Jun 1, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 17 untouched benchmarks
⏩ 15 skipped benchmarks1


Comparing ex3ndr:codex/js-virtual-mounts (0748c45) with main (4cae078)

Open in CodSpeed

Footnotes

  1. 15 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@codecov

codecov Bot commented Jun 1, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@ex3ndr ex3ndr marked this pull request as ready for review June 5, 2026 05:08

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3 issues found across 4 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="crates/monty-js/wrapper.ts">

<violation number="1" location="crates/monty-js/wrapper.ts:145">
P2: `writeBytesLimit` validation accepts NaN and Infinity, which silently disable write quota enforcement</violation>

<violation number="2" location="crates/monty-js/wrapper.ts:1128">
P2: Write limit quota is charged before backend write success, with no rollback on failure</violation>
</file>

<file name="crates/monty-js/src/monty_cls.rs">

<violation number="1" location="crates/monty-js/src/monty_cls.rs:685">
P1: Fallible callback setup after REPL/mount state is consumed can permanently orphan shared REPL state. After `take_shared_repl` removes the REPL from the shared `Arc<Mutex>`, fallible `CallbackStringPrint::new_js_ref` calls inside `feed_start`, `feed_with_mounts`, and `repl_progress_to_result_with_mounts` can error out via `?` before the REPL (or progress containing the REPL) is restored. On those early-return paths `put_shared_repl` is never reached, so the mutex stays `None` and the REPL is lost forever.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

macro_rules! feed_start_impl {
($repl:expr) => {{
let mut print_cb = match &print_callback_ref {
Some(func) => Some(CallbackStringPrint::new_js_ref(env, func)?),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Fallible callback setup after REPL/mount state is consumed can permanently orphan shared REPL state. After take_shared_repl removes the REPL from the shared Arc<Mutex>, fallible CallbackStringPrint::new_js_ref calls inside feed_start, feed_with_mounts, and repl_progress_to_result_with_mounts can error out via ? before the REPL (or progress containing the REPL) is restored. On those early-return paths put_shared_repl is never reached, so the mutex stays None and the REPL is lost forever.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At crates/monty-js/src/monty_cls.rs, line 685:

<comment>Fallible callback setup after REPL/mount state is consumed can permanently orphan shared REPL state. After `take_shared_repl` removes the REPL from the shared `Arc<Mutex>`, fallible `CallbackStringPrint::new_js_ref` calls inside `feed_start`, `feed_with_mounts`, and `repl_progress_to_result_with_mounts` can error out via `?` before the REPL (or progress containing the REPL) is restored. On those early-return paths `put_shared_repl` is never reached, so the mutex stays `None` and the REPL is lost forever.</comment>

<file context>
@@ -602,30 +631,98 @@ impl MontyRepl {
+        macro_rules! feed_start_impl {
+            ($repl:expr) => {{
+                let mut print_cb = match &print_callback_ref {
+                    Some(func) => Some(CallbackStringPrint::new_js_ref(env, func)?),
+                    None => None,
+                };
</file context>

if (this.mode !== 'read-only' && this.mode !== 'read-write') {
throw new TypeError("VirtualMount mode must be 'read-only' or 'read-write'")
}
if (options.writeBytesLimit != null && options.writeBytesLimit < 0) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: writeBytesLimit validation accepts NaN and Infinity, which silently disable write quota enforcement

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At crates/monty-js/wrapper.ts, line 145:

<comment>`writeBytesLimit` validation accepts NaN and Infinity, which silently disable write quota enforcement</comment>

<file context>
@@ -46,23 +47,179 @@ export type {
+    if (this.mode !== 'read-only' && this.mode !== 'read-write') {
+      throw new TypeError("VirtualMount mode must be 'read-only' or 'read-write'")
+    }
+    if (options.writeBytesLimit != null && options.writeBytesLimit < 0) {
+      throw new TypeError('writeBytesLimit must be non-negative')
+    }
</file context>
Suggested change
if (options.writeBytesLimit != null && options.writeBytesLimit < 0) {
if (options.writeBytesLimit != null && (!Number.isFinite(options.writeBytesLimit) || options.writeBytesLimit < 0)) {

mount.assertWritable(normalizedPath)
const data = getStringArg(call, 1, 'Path.write_text data')
const bytes = Buffer.byteLength(data)
mount.chargeWrite(bytes)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Write limit quota is charged before backend write success, with no rollback on failure

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At crates/monty-js/wrapper.ts, line 1128:

<comment>Write limit quota is charged before backend write success, with no rollback on failure</comment>

<file context>
@@ -612,6 +954,411 @@ export class MontyComplete {
+      mount.assertWritable(normalizedPath)
+      const data = getStringArg(call, 1, 'Path.write_text data')
+      const bytes = Buffer.byteLength(data)
+      mount.chargeWrite(bytes)
+      return normalizeWriteResult(callRequired(backend.writeText, 'writeText', normalizedPath, data), [...data].length)
+    }
</file context>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant